package org.hin.programmering1.engine;

import java.awt.Color;
import java.awt.Graphics;
import java.awt.Image;
import java.util.LinkedList;

import javax.swing.JFrame;

/**
 * GameEngine is the handler of the render and update loop
 * 
 * @author Meanz
 */
public abstract class GameEngine extends JFrame implements Runnable {
	/**
	 * How many updates per second we want, this is not frames.
	 * But number of times an object is updated each second.
	 * For example, movement etc...
	 */
	public final static int targetUPS = 60;
	
	/**
	 * Whether or not this engine is running
	 */
	private boolean running;
	/**
	 * The fps and ups values for the last second
	 */
	private int gameFPS, gameUPS;
	/**
	 * The number of milliseconds 
	 */
	private long renderTime;
	/**
	 * The image that we are drawing too
	 */
	private Image offScreenImage;
	private Graphics graphicsObject;
	/**
	 * The input system
	 */
	private Input input;
	
	/**
	 * List of game objects
	 */
	private LinkedList<GameObject> gameObjects = new LinkedList<GameObject>();
	private LinkedList<Renderable> gameRenderables = new LinkedList<Renderable>();
	
	/**
	 * Constructor
	 */
	public GameEngine() {
		super("Game Engine - HiN Programmering 1");
		gameFPS = 0;
		gameUPS = 0;
		renderTime = 0;
		input = new Input();
		addKeyListener(input);
		addMouseListener(input);
		addMouseMotionListener(input);
	}
	
	/**
	 * Loads and prepares the off screen image we are going to draw to
	 */
	public void loadOffScreenImage() {
		/**
		 * Create the image we are going to draw to
		 */
		offScreenImage = createImage(getWidth(), getHeight());
		graphicsObject = offScreenImage.getGraphics();
	}
	
	/**
	 * Set the size of this window
	 */
	public void setSize(int width, int height) {
		super.setSize(width, height);
		loadOffScreenImage();
	}
	
	/**
	 * Start the game engine
	 */
	public void start() {
		running = true;
		new Thread(this).start();
	}
	
	/**
	 * The main game loop
	 */
	public void run() {
		/**
		 * DeWitters GameLoop
		 */
		show();
		/**
         * Set some initial values
         */
        running = true;
        
        int TICKS_PER_SECOND = targetUPS;
        int SKIP_TICKS = 1000 / TICKS_PER_SECOND;
        int MAX_FRAMESKIP = 10;
        
        long last_game_tick = System.currentTimeMillis();
        long next_game_tick = last_game_tick;
        int loops;
        int fps = 0;
        int ups = 0;
        long last_fps = System.currentTimeMillis();
  
        gamePreload();
        /**
         * Start the render/update loop
         */
        while (running) {
            /**
             * Check for loop changes
             */
            if (TICKS_PER_SECOND != targetUPS) {
                TICKS_PER_SECOND = targetUPS;
                SKIP_TICKS = 1000 / TICKS_PER_SECOND;
            }
            /**
             * Logging
             */
            if (System.currentTimeMillis() - last_fps > 1000) {
                gameFPS = fps;
                gameUPS = ups;
                ups = 0;
                fps = 0;
                last_fps = System.currentTimeMillis();
            }
            /**
             * Update
             */
            loops = 0;
            while (System.currentTimeMillis() > next_game_tick && loops < MAX_FRAMESKIP) {
                internal_gameUpdate();
                long renderStart = System.currentTimeMillis();
                internal_gameRender();
                repaint();
                long renderEnd = System.currentTimeMillis();
                long deltaRender = renderEnd - renderStart;
                renderTime = (int) (deltaRender);
                next_game_tick += SKIP_TICKS;
                loops++;
                ups++;
            }
            /**
             * Render
             */
            try {
            Thread.sleep(1);
            } catch(InterruptedException ex) {
            	ex.printStackTrace();
            }
            fps++;
        } 
	}
	
	//Temporary method for some debugging
	public int getGameFPS() {
		return gameFPS;
	}
	
	/**
	 * Abstract methods
	 */
	public abstract void gamePreload();
	public abstract void gameUpdate();
	public abstract void gameRender(Graphics g);
	
	/**
	 * Updates all the game objects
	 */
	protected void internal_gameUpdate() {
		input.update(getLocation().x, getLocation().y);
		for(GameObject go : gameObjects) {
			if(go == null)
				continue;
			go.onUpdate();
		}
		gameUpdate();
	}
	
	/**
	 * Renders all the game engine attached renderable's
	 */
	protected void internal_gameRender() {
		graphicsObject.setColor(Color.gray);
		graphicsObject.fillRect(0, 0, getWidth(), getHeight());
		for(Renderable renderable : gameRenderables) {
			if(renderable == null)
				continue;
			renderable.render(graphicsObject);
		}
		gameRender(graphicsObject);
	}
	
	/**
	 * Adds a renderable object to the render queue
	 * @param re
	 */
	public void addRenderable(Renderable re) {
		gameRenderables.add(re);
	}
	
	/**
	 * Adds a game object to game object update queue
	 * @param go
	 */
	public void addGameObject(GameObject go) {
		gameObjects.add(go);
		go.onLoad(); //Load the game object
	}
	
	/**
	 * Draw the game
	 */
	@Override
	public void paint(Graphics g) {
		//super.paint(g);
		if(graphicsObject != null) {
			g.drawImage(offScreenImage, 0, 0, null);
		}
	}
	
}
